#include <stdio.h>
#include <string.h>
#include <tool/yinteger.h>

#ifdef  MAX_CHARS_UNSIGNED_INT_TO_STR
#undef  MAX_CHARS_UNSIGNED_INT_TO_STR
#endif
/** unsigned int    16 bits   0 to 65,535
                    32 bits   0 to 4,294,967,295
number of characters also 
includes the end of string NULL char, +1 to length 
32 bits max chars are 11
*/
#define MAX_CHARS_UNSIGNED_INT_TO_STR     11
#ifdef  MAX_CHARS_INT_TO_STR
#undef  MAX_CHARS_INT_TO_STR
#endif
/** int             16 bits   -32,768 to 32,767
                    32 bits   -2,147,483,648 to 2,147,483,647
number of characters also 
includes the end of string NULL char, +1 to length 
32 bits max chars are 12
*/
#define MAX_CHARS_INT_TO_STR              12
#ifdef  MAX_CHARS_UNSIGNED_LONG_TO_STR
#undef  MAX_CHARS_UNSIGNED_LONG_TO_STR
#endif
/** unsigned long   32 bits   0 to  4,294,967,295
                    64 bits   0 to 18,446,744,073,709,551,616
number of characters also 
includes the end of string NULL char, +1 to length 
32 bits max chars are 11 
64 bits max chars are 21
*/
#if _BIT_DEVICE == _32_BIT
#define MAX_CHARS_UNSIGNED_LONG_TO_STR    11
#elif _BIT_DEVICE == _64_BIT
#define MAX_CHARS_UNSIGNED_LONG_TO_STR    21
#endif
#ifdef  MAX_CHARS_LONG_TO_STR
#undef  MAX_CHARS_LONG_TO_STR
#endif
/** long            32 bits   -2,147,483,648 to 2,147,483,647
                    64 bits   -9,223,372,036,854,775,808 to
                               9,223,372,036,854,775,807
number of characters also 
includes the end of string NULL char, +1 to length 
32 bits max chars are 12 
64 bits max chars are 21
*/
#if _BIT_DEVICE == _32_BIT
#define MAX_CHARS_LONG_TO_STR   12
#elif _BIT_DEVICE == _64_BIT
#define MAX_CHARS_LONG_TO_STR   21
#endif

/** integer function data struct */
struct intFnData {
  unsigned char sizeof_char;
  unsigned char sizeof_unsigned_int;
};

/*======= declare internal function */
/*--- struct of integer function */
boolean yinteger_freeFn(intFn *fnP);
/* specific operation */
/*--- integer to string conversion */
char *yinteger_unsignedCharNumToStr(
    const intFn fn, const unsigned char num);
char *yinteger_charNumToStr(const intFn fn, const char num);
char *yinteger_unsignedIntNumToStr(
    const intFn fn, const unsigned int num);
char *yinteger_shortIntNumToStr(const intFn fn, const short int num);
char *yinteger_intNumToStr(const intFn fn, const int num);
char *yinteger_unsignedLongNumToStr(
    const intFn fn, const unsigned long num);
char *yinteger_longNumToStr(const intFn fn, const long num);
/*--- cloning */
size_t *yinteger_cloneUnsignedIntArray(const intFn fn, 
    const size_t *array, size_t len);

/*======= directly exposed function */
/** Create a <code>intFn</code> that provide 
the struct of integer function pointers.
@return a working struct of integer function pointers.
  or <code>NULL</code> if failure.
*/
intFn yinteger_createFn(void) {
  intFn fn;
  FN_MALLOC_ERRMSG(fn, sizeof(struct intFn), 
      "Error: yinteger_createFn(void) malloc(fn)")
  if (fn == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(fn->d, sizeof(struct intFnData), 
      "Error: yinteger_createFn(void) malloc(fn->d)")
  if (fn->d == NULL) {yinteger_freeFn(&fn); return NULL;}
  /*--- data ---*/
  fn->d->sizeof_char = sizeof(char);
  fn->d->sizeof_unsigned_int = sizeof(unsigned int);
  /*--- init function pointers ---*/
  fn->freeFn = yinteger_freeFn;
  /* specific operation */
  /*--- integer to string conversion */
  fn->unsignedCharNumToStr = yinteger_unsignedCharNumToStr;
  fn->charNumToStr = yinteger_charNumToStr;
  fn->unsignedIntNumToStr = yinteger_unsignedIntNumToStr;
  fn->shortIntNumToStr = yinteger_shortIntNumToStr;
  fn->intNumToStr = yinteger_intNumToStr;
  fn->unsignedLongNumToStr = yinteger_unsignedLongNumToStr;
  fn->longNumToStr = yinteger_longNumToStr;
  /*--- cloning */
  fn->cloneUnsignedIntArray = yinteger_cloneUnsignedIntArray;
  return fn;
}

/*======= exposed through function pointers */
/** Free a <code>intFn</code>, free up the memory.
@param fnP pass in the address of <code>intFn</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean yinteger_freeFn(intFn *fnP) {
  /* NOT critical if strFn is NULL, assume all's fine */
  if (*fnP == NULL) {return true;}
  if ((*fnP)->d != NULL) {
    /* free intFnData */
    free((*fnP)->d);
    (*fnP)->d = NULL;
  }
  /* free intFn */
  free(*fnP);
  *fnP = NULL;
  return true;
}

/*--- integer to string conversion */
/* NB: for following function:
unsigned char   8 bits    0 to 255
char            8 bits    -128 to 127
unsigned int    16 bits   0 to 65,535
                32 bits   0 to 4,294,967,295
short int       16 bits   -32,768 to 32,767
int             16 bits   -32,768 to 32,767
                32 bits   -2,147,483,648 to 2,147,483,647
unsigned long   32 bits   0 to 4,294,967,295
                64 bits   0 to 18,446,744,073,709,551,616
long            32 bits   -2,147,483,648 to 2,147,483,647
                64 bits   -9,223,372,036,854,775,808 to 
                           9,223,372,036,854,775,807
*/
/** Convert an unsigned char number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the unsigned char number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_unsignedCharNumToStr(
    const intFn fn, const unsigned char num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* unsigned char   8 bits    0 to 255 
    number of characters also 
    includes the end of string NULL char, +1 to length */
  int nChars;
  if (num < 10) {
    nChars = 2;
  } else if (num < 100) {
    nChars = 3;
  } else {
    nChars = 4;
  }
  /* allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, nChars * fn->d->sizeof_char, 
      "Error: yinteger_unsignedCharNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%u", num) == -1) {
    free(numStr);
    return NULL;
  }
  return numStr;
}

/** Convert a char number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the char number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_charNumToStr(const intFn fn, const char num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* char            8 bits    -128 to 127
    number of characters also 
    includes the end of string NULL char, +1 to length */
  int nChars;
  if (num < 0) {
    if (num > -10) {
      nChars = 3;
    } else if (num > -100) {
      nChars = 4;
    } else {
      nChars = 5;
    }
  } else { /* num >= 0 */
    if (num < 10) {
      nChars = 2;
    } else if (num < 100) {
      nChars = 3;
    } else {
      nChars = 4;
    }
  }
  /* allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, nChars * fn->d->sizeof_char, 
      "Error: yinteger_charNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%d", num) == -1) {
    free(numStr);
    return NULL;
  }
  return numStr;
}

/** Convert an unsigned int number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the unsigned int number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_unsignedIntNumToStr(
    const intFn fn, const unsigned int num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* unsigned int    16 bits   0 to 65,535
                     32 bits   0 to 4,294,967,295
    number of characters also 
    includes the end of string NULL char, +1 to length 
  int nChars;
  if (num < 10) {
    nChars = 2;
  } else if (num < 100) {
    nChars = 3;
  } else if (num < 1000) {
    nChars = 4;
  } else if (num < 10000) {
    nChars = 5;
  } else if (num < 100000) {
    nChars = 6;
  } else if (num < 1000000) {
    nChars = 7;
  } else if (num < 10000000) {
    nChars = 8;
  } else if (num < 100000000) {
    nChars = 9;
  } else if (num < 1000000000) {
    nChars = 10;
  } else {
    nChars = 11;
  } 
    allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, MAX_CHARS_UNSIGNED_INT_TO_STR * fn->d->sizeof_char, 
      "Error: yinteger_unsignedIntNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%u", num) == -1) {
    free(numStr);
    return NULL;
  }
  /* resize memory, perhaps free extras bytes 
    includes the end of string NULL char, +1 to length */
  char *tmpStr = realloc(numStr, strlen(numStr) +1);
  if (tmpStr == NULL) {
    free(numStr);
    return NULL;
  }
  return tmpStr;
}

/** Convert an short int number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the short int number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_shortIntNumToStr(const intFn fn, const short int num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* short int       16 bits   -32,768 to 32,767
    number of characters also 
    includes the end of string NULL char, +1 to length */
  int nChars;
  if (num < 0) {
    if (num > -10) {
      nChars = 3;
    } else if (num > -100) {
      nChars = 4;
    } else if (num > -1000) {
      nChars = 5;
    } else if (num > -10000) {
      nChars = 6;
    } else {
      nChars = 7;
    }
  } else { /* num >= 0 */
    if (num < 10) {
      nChars = 2;
    } else if (num < 100) {
      nChars = 3;
    } else if (num < 1000) {
      nChars = 4;
    } else if (num < 10000) {
      nChars = 5;
    } else {
      nChars = 6;
    }
  }
  /* allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, nChars * fn->d->sizeof_char, 
      "Error: yinteger_shortIntNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%d", num) == -1) {
    free(numStr);
    return NULL;
  }
  return numStr;
}

/** Convert an int number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the int number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_intNumToStr(const intFn fn, const int num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* int             16 bits   -32,768 to 32,767
                     32 bits   -2,147,483,648 to 2,147,483,647
    number of characters also 
    includes the end of string NULL char, +1 to length 
  int nChars;
  if (num < 0) {
    if (num > -10) {
      nChars = 3;
    } else if (num > -100) {
      nChars = 4;
    } else if (num > -1000) {
      nChars = 5;
    } else if (num > -10000) {
      nChars = 6;
    } else if (num > -100000) {
      nChars = 7;
    } else if (num > -1000000) {
      nChars = 8;
    } else if (num > -10000000) {
      nChars = 9;
    } else if (num > -100000000) {
      nChars = 10;
    } else if (num > -1000000000) {
      nChars = 11;
    } else {
      nChars = 12;
    }
  } else { // num >= 0 *
    if (num < 10) {
      nChars = 2;
    } else if (num < 100) {
      nChars = 3;
    } else if (num < 1000) {
      nChars = 4;
    } else if (num < 10000) {
      nChars = 5;
    } else if (num < 100000) {
      nChars = 6;
    } else if (num < 1000000) {
      nChars = 7;
    } else if (num < 10000000) {
      nChars = 8;
    } else if (num < 100000000) {
      nChars = 9;
    } else if (num < 1000000000) {
      nChars = 10;
    } else {
      nChars = 11;
    }
  }
    allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, MAX_CHARS_INT_TO_STR * fn->d->sizeof_char, 
      "Error: yinteger_intNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%d", num) == -1) {
    free(numStr);
    return NULL;
  }
  /* resize memory, perhaps free extras bytes 
    includes the end of string NULL char, +1 to length */
  char *tmpStr = realloc(numStr, strlen(numStr) +1);
  if (tmpStr == NULL) {
    free(numStr);
    return NULL;
  }
  return tmpStr;
}

/** Convert an unsigned long number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the unsigned long number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_unsignedLongNumToStr(
    const intFn fn, const unsigned long num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* unsigned long   32 bits   0 to 4,294,967,295
                     64 bits   0 to 18,446,744,073,709,551,616
    allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, MAX_CHARS_UNSIGNED_LONG_TO_STR * fn->d->sizeof_char, 
      "Error: yinteger_unsignedLongNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%lu", num) == -1) {
    free(numStr);
    return NULL;
  }
  /* resize memory, perhaps free extras bytes 
    includes the end of string NULL char, +1 to length */
  char *tmpStr = realloc(numStr, strlen(numStr) +1);
  if (tmpStr == NULL) {
    free(numStr);
    return NULL;
  }
  return tmpStr;
}

/** Convert an long number to string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> object to use.
@param num the long number.
@return a string if successful otherwise <code>NULL</code> if failure.
*/
char *yinteger_longNumToStr(const intFn fn, const long num) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  /* long            32 bits   -2,147,483,648 to 2,147,483,647
                     64 bits   -9,223,372,036,854,775,808 to
                                9,223,372,036,854,775,807
    allocate memory */
  char *numStr;
  FN_MALLOC_ERRMSG(numStr, MAX_CHARS_LONG_TO_STR * fn->d->sizeof_char, 
      "Error: yinteger_unsignedLongNumToStr(..) malloc(numStr)")
  if (numStr == NULL) {return NULL;}
  /* convert to string */
  if (sprintf(numStr, "%ld", num) == -1) {
    free(numStr);
    return NULL;
  }
  /* resize memory, perhaps free extras bytes 
    includes the end of string NULL char, +1 to length */
  char *tmpStr = realloc(numStr, strlen(numStr) +1);
  if (tmpStr == NULL) {
    free(numStr);
    return NULL;
  }
  return tmpStr;
}

/*--- cloning */
/** Duplicate an unsigned int array.
<p>Since this array is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>intFn</code> to use.
@param array the array
@param len the length of the array
@return new string or <code>NULL</code> if unsuccessful.
*/
size_t *yinteger_cloneUnsignedIntArray(const intFn fn, 
    const size_t *array, size_t len) {
  if (fn == NULL || fn->d == NULL || array == NULL || len <= 0) {
    return NULL;
  }
  size_t *newArray;
  FN_MALLOC_ERRMSG(newArray, len * fn->d->sizeof_unsigned_int, 
      "Error: yinteger_cloneUnsignedIntArray(..) malloc(newArray)")
  if (newArray == NULL) {return NULL;}
  do {
    len--;
    newArray[len] = array[len];
  } while (len > 0);
  return newArray;
}
